/*******************************************************************************

Copyright (c) 2003, Robert Cowham and Vaccaperna Systems Ltd.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL VACCAPERNA SYSTEMS LTD. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************/

/*******************************************************************************
 * Name:	p4clientapi.cpp
 *
 * Author:	Robert Cowham <robert@vaccaperna.co.uk>
 *
 * Description: 
 *		COM bindings for the Perforce API. User interface class
 * 		for getting Perforce results into COM.
 *
 ******************************************************************************/

#include "p4clientapi.h"
#include "TraceUtils.h"
#include "i18napi.h"

// Parse a space-delimited line.  Works similar to strtok, except that
//    fields must be separated by spaces, and fields that contain
//    spaces must be enclosed in double quotes.  White space before
//    or after a quoted field is ignored. Other white space is
//    significant.  Does not handle quotes embedded in quotes.  Does
//    handle empty fields.
//	  Expects the input and output to be wide strings (i.e. unicode capable)

class StrTok {
public:
	StrTok(BSTR s) : m_str(s), m_start(0) {
	}
	~StrTok() {}

	wstring spacetok()
	{
		static const WCHAR space = L' ';
		static const WCHAR quote = L'\"';
		wstring::size_type begin = m_start;
		wstring::size_type end;
		wstring ret;
		wstring::size_type i;

		if (begin >= m_str.length())
		{
			return ret;
		}

		if (m_start == 0)
		{
			m_searchfor = space;
		}
		i = begin;

		while ((i < m_str.length()) && (m_str[i] == space))	// skip spaces at start
		{
			++i;
		}

		if (i < m_str.length())
		{
			if (m_str[i] == quote)
			{
				m_searchfor = quote;	// remember for next time
				++i;
			}
			else
			{
				m_searchfor = space;
			}
			begin = i;

			while ((i < m_str.length()) && (m_str[i] != m_searchfor))
			{
				++i;
			}
		}

		if (i >= m_str.length())
		{
			end = m_str.length();
		}
		else
		{
			end = i;
			++i;
		}

		ret = m_str.substr(begin, end - begin);
		m_start = i;
		return ret;

	} // spacetok

private:
	wstring m_str;
	WCHAR m_searchfor;
	int m_start;
};


// Function which splits up a parameter string into an array of arguments
void
p4ClientApi::SplitArgs(BSTR cmd_line, StrBufArray *sba)
{
	USES_CONVERSION; 
	StrTok strtok(cmd_line);

	/* 
	* While there are tokens in "string" 
	*/
	wstring token = strtok.spacetok();
	while (token.length())
	{
		sba->Put((void *)m_ui.TranslateFromBSTR(token));
		token = strtok.spacetok();
	}

} // SplitArgs


p4ClientApi::p4ClientApi()
{
	STACK_TRACE("p4ClientApi::p4ClientApi")
    m_initCount = 0;
    m_tagged = 0;
    m_exceptionLevel = 2;
}

p4ClientApi::~p4ClientApi()
{
	STACK_TRACE("p4ClientApi::~p4ClientApi")
    if (m_initCount)
    {
		Error e;
		m_client.Final(&e);
		// Ignore errors
    }
}

int
p4ClientApi::Connect(StrBuf &errMsg)
{
	STACK_TRACE("p4ClientApi::Connect")
    if (m_initCount)
    {
		return S_OK;
    }

    Error	e;

    m_client.Init(&e);
    if (e.Test() && m_exceptionLevel)
	{
		StrBuf m;
		e.Fmt(&m);

		errMsg = m.Text();
		return E_FAIL;
	}

    if (e.Test())
		return E_FAIL;

    m_initCount++;
    return S_OK;
}


//
// Disconnect session
//
int
p4ClientApi::Disconnect()
{
	STACK_TRACE("p4ClientApi::Disconnect")
	if (!m_initCount)
	{
		return S_OK;
    }
    Error	e;
    m_client.Final( &e );

    m_initCount--;
    return S_OK;
}

void 
p4ClientApi::Tagged()
{
	STACK_TRACE("p4ClientApi::Tagged")
    m_client.SetProtocol("tag", "");
    m_tagged = 1;
}

void 
p4ClientApi::ParseForms()
{
	STACK_TRACE("p4ClientApi::ParseForms")
    m_client.SetProtocol("tag", "");
    m_client.SetProtocol("specstring", "");
    m_tagged = 1;
}

void p4ClientApi::CheckCharset()
{
	STACK_TRACE("p4ClientApi::CheckCharset")

	StrBuf s;
	s = GetCharset();
	TRACE1("p4com: Charset", s.Text());
	if (s.Length() > 0)
	{
		// Given that VB is full unicode - always use UTF8 and rely on VB layer
		// to do translation
		m_client.SetTrans(CharSetApi::UTF_8, CharSetApi::NOCONV, 
						CharSetApi::UTF_8, CharSetApi::UTF_8);
		m_ui.TranslateCharset(1);
	}
	else
	{
		m_ui.TranslateCharset(0);
	}
}

long
p4ClientApi::Run(BSTR completeCmd, StrBuf &errMsg)
{
	STACK_TRACE("p4ClientApi::Run")

    if (!m_initCount && m_exceptionLevel)
	{
		errMsg = "Not connected.";
		return E_FAIL;
	}

    if (!m_initCount)
		return E_FAIL;

    Error e;

	// Clear out any results from the previous command
    m_ui.Reset();
	CheckCharset();

	// Store away the parameters 
	StrBufArray args;
	SplitArgs(completeCmd, &args);

	// Construct argc and argv.
	int NumArgs = args.Count();
	int StartIndex = 1;
	int i;
	// use first element of args as cmd
	StrBuf cmd;
	cmd = (LPCSTR)args.Get(0);
	StartIndex = 1;
	NumArgs -= 1;

	char **argv = new char*[NumArgs];
	if (NumArgs > 0)
	{
		StrBuf s;

		for (i = StartIndex; i < args.Count(); i++)
		{
			s = (LPCSTR) args.Get(i);
			argv[i - StartIndex] = new char[s.Length() + 1];
			strcpy(argv[i - StartIndex], s.Text());
		}
	}
    m_client.SetArgv( NumArgs, argv );
    m_client.Run( cmd.Text(), &m_ui );

	for (i = 0; i < NumArgs; i++)
	{
		delete [] argv[i];
	}
	delete [] argv;

	m_ui.CloseFiles();

    return S_OK;
}


void GetMsg(StrBuf &msg, StrBufArray *sba)
{
	for (int i = 0; i < sba->Count(); i++)
	{
		msg.Append((LPCSTR)sba->Get(i));
		msg.Append(" ");
	}
}

StrBuf
p4ClientApi::ErrorMsg()
{
	STACK_TRACE("p4ClientApi::ErrorMsg")
	StrBuf errMsg;

	// Create msg from error array (if exists)
	if (ExceptionLevel() > 0)
	{
		if (ExceptionLevel() == 1)
		{
			GetMsg(errMsg, m_ui.ErrorArray());
		}
		else
		{
			GetMsg(errMsg, m_ui.WarningArray());
			GetMsg(errMsg, m_ui.ErrorArray());
		}
	}
	return errMsg;
}